/* Copyright 2010 Michael Wenders, michaelwenders <at> gmx <dot> de

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "lcm_includes.h"

Lcm::Lcm()
{
	logger = new lcm::log::Logger("/var/log/lcm/lcm.log",lcm::log::LOGGER_DEBUG,lcm::log::LOGGER_DEBUG);
	config = new LcmConfig("/etc/lcm/lcm.conf");
}
Lcm::~Lcm()
{
	//FIXME
	//delete logger;
	//delete config;
}

int Lcm::init(int argc, char* argv[])
{
	logger->write("Initialize lcm",lcm::log::LOGGER_DEBUG);
	logger->write("Setting defaults",lcm::log::LOGGER_DEBUG);

	/* Initialize config backend */
	config->setDefaults();

	logger->write("Loading config file",lcm::log::LOGGER_DEBUG);
	config->loadFile();

	logger->write("Parsing command line",lcm::log::LOGGER_DEBUG);
	switch(this->parseCommandLine(argc,argv))
	{
	case lcm::error::CMDLINE_BREAK: // everything ok, just quit, because user doesn't want to run lcmd really
		logger->write("Break parsing command line, this is normal",lcm::log::LOGGER_DEBUG);
		return lcm::error::CMDLINE_BREAK;

	case lcm::error::CMDLINE_MESSED_UP: // parameter messed up, show usage and quit
		logger->write("Parameter messed up",lcm::log::LOGGER_ERROR);
		//this->showUsage();
		return lcm::error::CMDLINE_MESSED_UP;

	case lcm::error::CMDLINE_MISSING_OPTION:
		logger->write("Missing option after parameter",lcm::log::LOGGER_ERROR);
		return lcm::error::CMDLINE_MISSING_OPTION;

	case 0:
		return 0;

	default: //FIXME
		return 0;
	}

}

int Lcm::run()
{
	string text = "Starting lcm "; text += LCM_VERSION;
	logger->write(text,lcm::log::LOGGER_INFO);

	text = "Initialize network backend (server: "; text += config->getIP();
	text += ":"; text += config->getPort();
	logger->write("Initialize network backend",lcm::log::LOGGER_DEBUG);

	lcmNetwork = new LcmNetwork(config->getIP(),config->getPort());
	lcmNetwork->init();
	//FIXME

	///
	/// DEBUG
	///

	string testtext = "Hello World";
	cout << "Writing " << testtext << " to Server" << endl;
	RSA* rsa = RSA_new();
	rsa = lcm::ssl::initSSLPubKey("/home/regioit/lcmd.pem");

	if(!rsa)
	{
		cout << "Something wrong with RSA while getting PubKey" << endl;
	}
	lcm::ssl::writeSSLSocket(lcmNetwork->getClientSocket(),testtext, rsa);

	exit(31);

	//ENDE DEBUG

	/* main part */
	if(lcmNetwork->sendGreeting() != 0)
	{
		//FIXME error could not write
	}
	else
	{ // main loop
		string answer = "";
		bool quit = false;
		string scriptname = "";
		string scriptfile = "";

		while(!quit)
		{
			lcm::readSocket(lcmNetwork->getClientSocket(),answer);
			switch(lcm::stringToInt(answer))
			{
			case lcm::lcmp::LCMP_VERSION:
				lcmNetwork->sendVersion();
				break;
			case lcm::lcmp::LCMP_CLIENTNAME:
				lcmNetwork->sendHostname();
				break;
			case lcm::lcmp::LCMP_INCOMPATIBLE:
				logger->write("This version of lcm is incompatible with the server version",lcm::log::LOGGER_ERROR);
				quit = true;
				break;
			case lcm::lcmp::LCMP_HOSTUNKNOWN:
				logger->write("This client does not appear in server's database",lcm::log::LOGGER_ERROR);
				quit = true;
				break;
			case lcm::lcmp::LCMP_GOODBYE:
				quit = true;
				break;
			case lcm::lcmp::LCMP_SCRIPT:
				lcm::writeSocket(lcmNetwork->getClientSocket(),"OK");
				scriptname = "";
				lcm::readSocket(lcmNetwork->getClientSocket(),scriptname);
				lcm::writeSocket(lcmNetwork->getClientSocket(),"OK");
				scriptfile = "/tmp/"; scriptfile += scriptname;
				this->recvScript(lcmNetwork->getClientSocket(),scriptfile.c_str());
				lcm::writeSocket(lcmNetwork->getClientSocket(),lcm::intToString(this->executeScript(scriptfile)));
				break;
			case lcm::lcmp::LCMP_DENY:
				logger->write("Server denied connection",lcm::log::LOGGER_ERROR);
				quit = true;
				break;
			default:
				logger->write("Server sent an unknown command",lcm::log::LOGGER_ERROR);
				break;
			} // </switch>
		} // </while>
	} // </main loop>



	return OK;
}

int Lcm::parseCommandLine(int argc, char* argv[])
{
	struct struct_flags {
	    bool help;
	    bool version;
	    bool verbosity;
	    bool port;
	    bool file;
	    int whichPort;
	    string whichFile;
	};
	struct_flags flags{0,0,0,0,0,0,""};

	static const char *optString = "h?Vvp:F:";

	static const struct option longOpts[] = {
		{ "help", no_argument, NULL, 'h' },
		{ "version", no_argument, NULL, 'V' },
		{ "verbose", no_argument, NULL, 'v' },
		{ "port", required_argument, NULL, 'p' },
		{ "file", required_argument, NULL, 'F' },
		{ NULL, no_argument, NULL, 0 }
	};

	int longIndex = 0, opt = 0;
	opt = getopt_long( argc, argv, optString, longOpts, &longIndex );
	while( opt != -1 )
	{
		switch( opt )
		{
			case 'h':
			case '?':
				flags.help = true;
				break;

			case 'V':
				flags.version = true;
				break;

			case 'v':
				flags.verbosity = true;
				break;

			case 'p':
				flags.port = true;
				flags.whichPort = atoi(optarg);
				break;

			case 'F':
				flags.file = true;
				flags.whichFile = optarg;
				break;

			case 0:     /* long option without a short arg */
				break;

			default:
				/* You won't actually get here. */
				break;
		}

		opt = getopt_long( argc, argv, optString, longOpts, &longIndex );
	}

	/* check flags and react */

	if(flags.version)
	{
		cout << LCM_VERSION << endl;
		return lcm::error::CMDLINE_BREAK;
	}

	if(flags.verbosity)
	{
		//config->increaseVerbosity();
	}

	if(flags.help)
	{
		this->showUsage();
		return lcm::error::CMDLINE_BREAK;
	}

	if(flags.port)
	{
		config->setPort(flags.whichPort);
	}

	if(flags.file)
	{
		config->setFilename(flags.whichFile);
	}

	return 0;
}

string Lcm::recvScript(const int socket, const char* filename)
{
	int bytesSent;
	char *buffer = (char*)malloc (512);
	string text = "";
	bool fileSent = false;

	do
	{
		bytesSent = read(socket,buffer,512);
		if(bytesSent == -1)
		{
			return "NOT_OK";
		}
		else if(bytesSent == 0)
			fileSent = true;
		else if(buffer[bytesSent-1] == '\0')
		{
			text += buffer;
			fileSent = true;
		}
		else
		{
			text += buffer;
			fileSent = false;
		}

	}while(fileSent == false);

	fstream scriptFile;
	scriptFile.open(filename,ios::trunc | ios::out);
	if(!scriptFile)
		cout << "Could not write to file" << endl;
	scriptFile << text;
	scriptFile.close();

	return text;
}

int Lcm::executeScript(string scriptfile)
{
	chmod(scriptfile.c_str(),0550);
	writeMetaScript(scriptfile);
	lcm::stringToBash("/tmp/metascript.sh");

	return lcm::stringToInt(lcm::stringToBash("cat /tmp/retCode"));
}

void Lcm::writeMetaScript(string scriptfile)
{
	fstream metascript;
	metascript.open("/tmp/metascript.sh",ios::trunc | ios::out);
	if(!metascript)
		cout << "Could not write to file" << endl;
	metascript << "#! /bin/bash" << endl;
	metascript << "bash " << scriptfile << endl;
	metascript << "echo $? > /tmp/retCode" << endl;
	metascript.close();
	chmod("/tmp/metascript.sh",0700);
}

void Lcm::showUsage()
{
	cout << "Lcm Version " << LCM_VERSION << endl;
	cout << "Usage: lcm [Options]" << endl;
	cout << "Options:" << endl;
	cout << "    -p (--port)\t\tDefine a portnumber for connecting to lcmd" << endl;
	cout << endl;
	cout << "    -F (--file)\t\tDefine another configfile" << endl;
	cout << endl;
	cout << "    -v (--verbose)\tIncrease verbosity" << endl;
	cout << endl;
	cout << "    -V (--version)\tShow Version" << endl;
	cout << endl;
	cout << "    -h (-?/--help)\tShow Help" << endl;
	cout << endl;
	cout << "Bugs to: michaelwenders@gmx.de" << endl;

}

